﻿
//
// 版权所有 (c) 2021-2023 JR
//
// 特此免费授予获得本软件的任何人以处理本软件的权利，但须遵守以下条件：在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
//
// 软件按“原样”提供，不提供任何形式的明示或暗示的保证，包括但不限于对适销性、适用性和非侵权的保证。
// 在任何情况下，作者或版权持有人均不对任何索赔、损害或其他责任负责，无论是因合同、侵权或其他方式引起的，与软件或其使用或其他交易有关。

using FluentEmail.Core;
using Microsoft.AspNetCore.Components.Forms;
using NewLife.Serialization;
using Npgsql.Logging;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Logical;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
using RazorEngine.Text;
using SqlSugar;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Compression;
using static SKIT.FlurlHttpClient.Wechat.Api.Models.CgibinGetCurrentSelfMenuInfoResponse.Types;
using static SKIT.FlurlHttpClient.Wechat.Api.Models.TCBGetPressureTestReportResponse.Types;

namespace EMC.VISITOR.Core.Service;

/// <summary>
/// 系统代码生成器服务
/// </summary>
[ApiDescriptionSettings(Order = 270)]
public class SysCodeGenService : IDynamicApiController, ITransient
{
    private readonly ISqlSugarClient _db;

    private readonly SysCodeGenConfigService _codeGenConfigService;
    private readonly IViewEngine _viewEngine;
    private readonly CodeGenOptions _codeGenOptions;
    private readonly SysCacheService _sysCacheService;
    public SysCodeGenService(ISqlSugarClient db,
        SysCodeGenConfigService codeGenConfigService,
        IViewEngine viewEngine,
        IOptions<CodeGenOptions> codeGenOptions,
        SysCacheService sysCacheService)
    {
        _db = db;
        _codeGenConfigService = codeGenConfigService;
        _viewEngine = viewEngine;
        _codeGenOptions = codeGenOptions.Value;
        _sysCacheService = sysCacheService;
    }

    /// <summary>
    /// 获取代码生成分页列表
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("获取代码生成分页列表")]
    public async Task<SqlSugarPagedList<SysCodeGen>> Page(CodeGenInput input)
    {
        var data = await _db.Queryable<SysCodeGen>()
            .Where(r => r.IsDelete == false)
            .WhereIF(!string.IsNullOrWhiteSpace(input.TableName), u => u.TableName.Contains(input.TableName.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.BusName), u => u.BusName.Contains(input.BusName.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.AuthorName), u => u.AuthorName.Contains(input.AuthorName.Trim()))
            .ToPagedListAsync(input.Page, input.PageSize);
        data.Items.ForEach(r =>
        {
            if (r.NormalButtons == null || r.NormalButtons.Count <= 0)
            {
                r.NormalButtons = new List<NormalButton>
                     {
                        new NormalButton{ Desc= "新增", ButtonName= "add", IsUse= true},
                        new NormalButton{ Desc="刪除", ButtonName="delete",IsUse=true},
                        new NormalButton{ Desc="编辑", ButtonName="edit",IsUse=true},
                        new NormalButton{ Desc= "导出", ButtonName= "export",IsUse= true},
                        new NormalButton{ Desc= "导入", ButtonName= "import",IsUse= true},
                        new NormalButton{ Desc= "保存", ButtonName= "save",IsUse= true},
                        new NormalButton{ Desc= "详情", ButtonName= "detail",IsUse= true},
                        new NormalButton{ Desc= "下载导入模板", ButtonName= "templateDown",IsUse= true}
                     };
            }
        });
        return data;
    }

    //[ApiDescriptionSettings(Name = "OnlyAddMenu"), HttpPost]
    //[DisplayName("增加菜单")]
    //public async Task OnlyAddMenu(AddCodeGenInput input)
    //{
    //    var model = await _db.Queryable<SysCodeGen>().Where(u => u.TableName == input.TableName).FirstAsync();
    //    if (model != null)
    //    {
    //        var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = model.Id }); // 字段集合
    //        await AddMenu(input.TableName, input.BusName, input.MenuPid, tableFieldList, model.Comments);
    //    }
    //    else
    //    {
    //        throw Oops.Oh(ErrorCodeEnum.D1400);
    //    }
    //}
    /// <summary>
    /// 增加代码生成
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Add"), HttpPost]
    [DisplayName("增加代码生成")]
    public async Task AddCodeGen(AddCodeGenInput input)
    {
        var isExist = await _db.Queryable<SysCodeGen>().Where(u => u.TableName == input.TableName).AnyAsync();
        if (isExist)
            throw Oops.Oh(ErrorCodeEnum.D1400);

        var codeGen = input.Adapt<SysCodeGen>();
        codeGen.NormalButtons.ForEach(r =>
        {
            if (r.ButtonName == "delete")
                r.Desc = "刪除";
        });
        var newCodeGen = await _db.Insertable(codeGen).ExecuteReturnEntityAsync();
        if (input.CopyOtherTableConfig.IsNullOrEmpty())
        {
            // 加入配置表中
            _codeGenConfigService.AddList(GetColumnList(input), newCodeGen);
        }
        else
        {
            var copyConfig = await _db.Queryable<SysCodeGen>()
                .Includes(r => r.Configs)
                .Where(u => u.TableName == input.CopyOtherTableConfig.Trim()).FirstAsync();
            if (copyConfig == null)
                throw Oops.Oh(ErrorCodeEnum.D1401);

            copyConfig.Configs.ForEach(r =>
            {
                r.Id = default(long);
                r.CodeGenId = newCodeGen.Id;
            });
            await _db.Insertable(copyConfig.Configs).ExecuteCommandAsync();
        }
    }


    [ApiDescriptionSettings(Name = "ReLoadConfigs"), HttpPost]
    [DisplayName("重新加载配配置")]
    public async Task ReLoadConfigs(UpdateCodeGenInput input)
    {
        var newCodeGen = input.Adapt<SysCodeGen>();
        //获取实体所有的列
        var genConfigColumns = GetColumnList(new AddCodeGenInput { ConfigId = input.ConfigId, TableName = input.TableName });
        //获取配置中存在的列
        var exsitConfigs = await _db.Queryable<SysCodeGenConfig>().Where(r => r.CodeGenId == input.Id).ToListAsync();
        //实体所有的列排除库里的配置列 (新增的列)
        var exceptConfigColumnStrings = genConfigColumns.Select(r => r.ColumnName).Except(exsitConfigs.Select(r => r.ColumnName)).ToList();
        var addColumns = genConfigColumns.Where(r => exceptConfigColumnStrings.Contains(r.ColumnName)).ToList();
        //列的类型不一致，需要修改
        List<SysCodeGenConfig> updateList = new List<SysCodeGenConfig>();
        List<SysCodeGenConfig> changeColumnDescriptionList = new List<SysCodeGenConfig>();
        genConfigColumns.ForEach(r =>
        {
            var dbConfig = exsitConfigs.FirstOrDefault(c => c.ColumnName == r.ColumnName && c.NetType != r.NetType);
            if (dbConfig != null)
            {

                dbConfig.NetType = r.DataType;
                dbConfig.DataType = r.DataType;
                //dbConfig.EffectType = CodeGenUtil.DataTypeToEff(r.NetType);
                dbConfig.ColumnComment = r.ColumnComment;
                updateList.Add(dbConfig);
            }
            var changeDescription = exsitConfigs.FirstOrDefault(c => c.ColumnName == r.ColumnName && (c.ColumnComment.IsNullOrEmpty() || c.ColumnComment == c.ColumnName));
            if (changeDescription != null)
            {
                changeDescription.ColumnComment = r.ColumnComment;
                changeColumnDescriptionList.Add(changeDescription);
            }
        });

        //删除列
        //表明实体里字段不存在了，代码生成配置表里字段还存在，删除代码表里的数据
        var exsitCoumns = exsitConfigs.Select(r => r.ColumnName).Except(genConfigColumns.Select(r => r.ColumnName)).ToList();
        if (exsitCoumns.Count > 0)
        {
            await _codeGenConfigService.DeleteCodeGenConfigs(exsitConfigs.Where(z => exsitCoumns.Contains(z.ColumnName)).ToList());
        }
        if (updateList.Count > 0)
            await _db.Updateable<SysCodeGenConfig>(updateList).UpdateColumns(r => new { r.NetType, r.DataType }).ExecuteCommandAsync();

        if (changeColumnDescriptionList.Count > 0)
            await _db.Updateable<SysCodeGenConfig>(changeColumnDescriptionList).UpdateColumns(r => new { r.ColumnComment }).ExecuteCommandAsync();
        //新增列
        if (addColumns.Count > 0)
            _codeGenConfigService.AddList(addColumns, newCodeGen);
    }

    /// <summary>
    /// 更新代码生成
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Update"), HttpPost]
    [UnitOfWork(true)]
    [DisplayName("更新代码生成")]
    public async Task UpdateCodeGen(UpdateCodeGenInput input)
    {
        var isExist = await _db.Queryable<SysCodeGen>().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id);
        if (isExist)
            throw Oops.Oh(ErrorCodeEnum.D1400);

        await _db.Updateable(input.Adapt<SysCodeGen>()).ExecuteCommandAsync();
        if (!input.CopyOtherTableConfig.IsNullOrEmpty())
        {
            var copyConfig = await _db.Queryable<SysCodeGen>()
                 .Includes(r => r.Configs)
                 .Where(u => u.TableName == input.CopyOtherTableConfig.Trim()).FirstAsync();
            if (copyConfig == null)
                throw Oops.Oh(ErrorCodeEnum.D1401);

            copyConfig.Configs.ForEach(r =>
            {
                r.Id = default(long);
                r.CodeGenId = input.Id;
            });
            await _db.Deleteable<SysCodeGenConfig>().Where(r => r.CodeGenId == input.Id).ExecuteCommandAsync();
            await _db.Insertable(copyConfig.Configs).ExecuteCommandAsync();
        }
    }

    /// <summary>
    /// 删除代码生成
    /// </summary>
    /// <param name="inputs"></param>
    /// <returns></returns>
    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
    [DisplayName("删除代码生成")]
    public async Task DeleteCodeGen(List<DeleteCodeGenInput> inputs)
    {
        if (inputs == null || inputs.Count < 1) return;

        var codeGenConfigTaskList = new List<Task>();
        inputs.ForEach(u =>
        {
            _db.Deleteable<SysCodeGen>().In(u.Id).ExecuteCommand();

            // 删除配置表中
            codeGenConfigTaskList.Add(_codeGenConfigService.DeleteCodeGenConfig(u.Id));
        });
        await Task.WhenAll(codeGenConfigTaskList);
    }

    /// <summary>
    /// 获取代码生成详情
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("获取代码生成详情")]
    public async Task<SysCodeGen> GetDetail([FromQuery] QueryCodeGenInput input)
    {
        return await _db.Queryable<SysCodeGen>().SingleAsync(m => m.Id == input.Id);
    }

    /// <summary>
    /// 获取数据库库集合
    /// </summary>
    /// <returns></returns>
    [DisplayName("获取数据库库集合")]
    public async Task<List<DatabaseOutput>> GetDatabaseList()
    {
        var dbCongigs = App.GetOptions<DbConnectionOptions>().ConnectionConfigs;
        return await Task.FromResult(dbCongigs.Adapt<List<DatabaseOutput>>());
    }

    /// <summary>
    /// 获取数据库表(实体)集合
    /// </summary>
    /// <returns></returns>
    [DisplayName("获取数据库表(实体)集合")]
    public async Task<List<TableOutput>> GetTableList(string configId = SqlSugarConst.MainConfigId)
    {
        var provider = _db.AsTenant().GetConnectionScope(configId);
        var dbTableInfos = provider.DbMaintenance.GetTableInfoList(false); // 不能走缓存,否则切库不起作用

        var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId == configId);

        var dbTableNames = dbTableInfos.Select(u => u.Name.ToLower()).ToList();
        IEnumerable<EntityInfo> entityInfos = await GetEntityInfos();

        var tableOutputList = new List<TableOutput>();
        foreach (var item in entityInfos)
        {
            //排除非当前项目架构的数据表
            var table = dbTableInfos.FirstOrDefault(x => x.Name.ToLower() == SqlSugarConst.GetTableName(config, item.DbTableName).ToLower());
            if (table == null) continue;
            tableOutputList.Add(new TableOutput
            {
                ConfigId = configId,
                EntityName = item.EntityName,
                TableName = table.Name,
                TableComment = item.TableDescription
            });
        }
        return tableOutputList;
    }

    /// <summary>
    /// 根据表名获取列集合
    /// </summary>
    /// <returns></returns>
    [DisplayName("根据表名获取列集合")]
    public List<ColumnOuput> GetColumnListByTableName([Required] string tableName, string configId = SqlSugarConst.MainConfigId)
    {
        // 切库---多库代码生成用
        var provider = _db.AsTenant().GetConnectionScope(configId);

        var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId == configId);
        // 获取实体类型属性
        var entityType = provider.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == tableName);
        if (entityType == null) return null;
        var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)];

        var modelType = GetEntityInfos().GetAwaiter().GetResult()
            .FirstOrDefault(m =>
            (config.TableSettings?.TablePrefix + m.EntityName + config.TableSettings?.TableSuffix).ToUpper() == tableName.ToUpper() ||
            (config.TableSettings?.TablePrefix + m.DbTableName + config.TableSettings?.TableSuffix).ToUpper() == tableName.ToUpper()
            );
        var entityDescriptionList = modelType.Type.GetProperties().Select(r => new
        {
            r.Name,
            SugarColumnName = (r.GetCustomAttribute<SugarColumn>() == null ? null : r.GetCustomAttribute<SugarColumn>().ColumnName)
        });


        Func<string, string> covToModelFiledName = (dbColumnName) =>
        {
            string needModelName = string.Empty;
            var modelFiled = entityDescriptionList.FirstOrDefault(r => r.Name.ToUpper() == dbColumnName.ToUpper());
            if (modelFiled != null)
            {
                needModelName = modelFiled.Name;
                needModelName = modelFiled.SugarColumnName ?? needModelName;
            }
            return needModelName;
        };

        return provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput
        {
            ColumnName = config.DbSettings.EnableUnderLine ? covToModelFiledName(CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames)) : covToModelFiledName(u.DbColumnName),
            ColumnKey = u.IsPrimarykey.ToString(),
            DataType = u.DataType.ToString(),
            NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType),
            ColumnComment = u.ColumnDescription
        }).ToList();
    }

    /// <summary>
    /// 获取数据表列（实体属性）集合
    /// </summary>
    /// <returns></returns>
    private List<ColumnOuput> GetColumnList([FromQuery] AddCodeGenInput input)
    {
        var entityType = GetEntityInfos().GetAwaiter().GetResult().FirstOrDefault(m => m.EntityName == input.TableName);
        if (entityType == null)
            return null;

        // 切库---多库代码生成用
        var provider = _db.AsTenant().GetConnectionScope(!string.IsNullOrEmpty(input.ConfigId) ? input.ConfigId : SqlSugarConst.MainConfigId);

        var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId == input.ConfigId);
        //var dbTableName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(entityType.DbTableName) : entityType.DbTableName;
        //dbTableName = config.TableSettings.TablePrefix + dbTableName + config.TableSettings.TableSuffix;
        var dbTableName = SqlSugarConst.GetTableName(config, entityType.DbTableName);
        var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)];
        var columnInfos = provider.DbMaintenance.GetColumnInfosByTableName(dbTableName, false);

        var entityDescriptionList = entityType.Type.GetProperties().Select(r => new
        {
            r.Name,
            Description = (r.GetCustomAttribute<SugarColumn>() == null ? null : r.GetCustomAttribute<SugarColumn>().ColumnDescription)
        });


        var result = columnInfos.Select(u => new ColumnOuput
        {
            // 转下划线后的列名需要再转回来（暂时不转）
            //ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName,
            ColumnName = u.DbColumnName,
            ColumnLength = u.Length,
            IsPrimarykey = u.IsPrimarykey,
            IsNullable = u.IsNullable,
            ColumnKey = u.IsPrimarykey.ToString(),
            NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType),
            dbColumnInfo = u,
            DataType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType),
            ColumnComment = string.IsNullOrWhiteSpace(u.ColumnDescription) ? entityDescriptionList.FirstOrDefault(z => z.Name == u.DbColumnName)?.Description : u.ColumnDescription
        }).ToList();

        // 获取实体的属性信息，赋值给PropertyName属性(CodeFirst模式应以PropertyName为实际使用名称)
        var entityProperties = entityType.Type.GetProperties();

        for (int i = result.Count - 1; i >= 0; i--)
        {
            var columnOutput = result[i];
            // 先找自定义字段名的
            var propertyInfo = entityProperties.FirstOrDefault(p => (p.GetCustomAttribute<SugarColumn>()?.ColumnName ?? "").ToLower() == columnOutput.ColumnName.ToLower());
            // 如果找不到就再找自动生成字段名的(并且过滤掉没有SugarColumn的属性)
            if (propertyInfo == null)
            {
                //propertyInfo = entityProperties.FirstOrDefault(p => p.GetCustomAttribute<SugarColumn>() != null && p.Name == (config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(columnOutput.ColumnName, entityBasePropertyNames) : columnOutput.ColumnName));
                propertyInfo = entityProperties.FirstOrDefault(p => p.GetCustomAttribute<SugarColumn>() != null && p.Name.ToLower() == columnOutput.ColumnName.ToLower());
            }
            if (propertyInfo != null)
            {
                columnOutput.PropertyName = propertyInfo.Name;
                columnOutput.ColumnName = columnOutput.PropertyName; //pgsql 存到数据中会导致列名变成小写
                //columnOutput.DataType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType);
            }
            else
                result.RemoveAt(i); //移除没有定义此属性的字段
        }
        return result;
    }

    /// <summary>
    /// 获取库表信息
    /// </summary>
    /// <returns></returns>
    private async Task<IEnumerable<EntityInfo>> GetEntityInfos()
    {
        var entityInfos = new List<EntityInfo>();

        var type = typeof(SugarTable);
        var types = new List<Type>();
        if (_codeGenOptions.EntityAssemblyNames != null)
        {
            foreach (var assemblyName in _codeGenOptions.EntityAssemblyNames)
            {
                Assembly asm = Assembly.Load(assemblyName);
                types.AddRange(asm.GetExportedTypes().ToList());
            }
        }
        bool IsMyAttribute(Attribute[] o)
        {
            foreach (Attribute a in o)
            {
                if (a.GetType() == type)
                    return true;
            }
            return false;
        }
        Type[] cosType = types.Where(o =>
        {
            return IsMyAttribute(Attribute.GetCustomAttributes(o, true));
        }
        ).ToArray();

        foreach (var c in cosType)
        {
            var sugarAttribute = c.GetCustomAttributes(type, true)?.FirstOrDefault();

            var des = c.GetCustomAttributes(typeof(DescriptionAttribute), true);
            var description = "";
            if (des.Length > 0)
            {
                description = ((DescriptionAttribute)des[0]).Description;
            }
            entityInfos.Add(new EntityInfo()
            {
                EntityName = c.Name,
                DbTableName = sugarAttribute == null ? c.Name : ((SugarTable)sugarAttribute).TableName,
                TableDescription = description,
                Type = c
            });
        }
        return await Task.FromResult(entityInfos);
    }

    [DisplayName("代码生成到本地App")]
    public async Task<dynamic> RunLocalApp(SysCodeGen input)
    {
        return await RunLocalPrivate(input, CodeGenType.APP);
    }

    /// <summary>
    /// 代码生成到本地
    /// </summary>
    /// <returns></returns>
    [DisplayName("代码生成到本地")]
    public async Task<dynamic> RunLocal(SysCodeGen input)
    {
        return await RunLocalPrivate(input, CodeGenType.PC);
    }
    enum CodeGenType
    {
        PC,
        APP
    }
    class UniAppPages
    {
        public List<page> pages { get; set; } = new List<page>();
        public dynamic subPackages { get; set; }
        public dynamic globalStyle { get; set; }
        public dynamic tabBar { get; set; }
        public class page
        {
            public string path { get; set; }
            public dynamic style { get; set; }
        }
    }
    private void WriteAppPagesJson(SysMenu menu)
    {
        var appJsonFilePath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.AppCodeGenFile, "pages.json");
        var readString = FileHelper.ReadFile(appJsonFilePath, Encoding.UTF8);
        var appPages = JsonConvert.DeserializeObject<UniAppPages>(readString);
        bool needWritePageJson = false;
        if (appPages.pages.Any(r => r.style == null))
        {
            appPages.pages.ForEach(r =>
            {
                if (r.style == null)
                {
                    r.style = new { };
                }
            });
        }
        if (!appPages.pages.Any(r => r.path.Contains(menu.LinkUrl.TrimStart('/'))))
        {
            appPages.pages.Add(new UniAppPages.page
            {
                path = menu.LinkUrl.TrimStart('/'),
                style = new { navigationBarTitleText = menu.Title, enablePullDownRefresh = false, navigationStyle = "custom" }
            });
            needWritePageJson = true;
        }
        if (!appPages.pages.Any(r => r.path.Contains(menu.EditUrl.TrimStart('/'))))
        {
            appPages.pages.Add(new UniAppPages.page
            {
                path = menu.EditUrl.TrimStart('/'),
                style = new { navigationBarTitleText = $"{menu.Title}编辑", enablePullDownRefresh = false, navigationStyle = "custom" }
            });
            needWritePageJson = true;
        }
        if (needWritePageJson)
        {
            //new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore };
            string writePagesJsonString = JsonConvert.SerializeObject(appPages);
            FileHelper.WriteFile(appJsonFilePath, writePagesJsonString, Encoding.UTF8, true);
        }
        //var config = await _db.Queryable<SysConfig>().FirstAsync(r => r.Code == "app_pages.json");
        //if (config == null)
        //{
        //    config = new SysConfig();
        //    config.Name = "App端路由配置";
        //    config.Code = "app_pages.json";
        //    config.SysFlag = YesNoEnum.N;
        //    config.GroupCode = "App";
        //}
        //config.Value = pagesJsonString;
        //if (config.Id > 0)
        //    await _db.MyInsertable<SysConfig>(config).ExecuteCommandAsync();
        //else
        //    await _db.MyUpdateable<SysConfig>(config).UpdateColumns(r => new { r.Value }).ExecuteCommandAsync();
    }
    private async Task<dynamic> RunLocalPrivate(SysCodeGen input, CodeGenType type)
    {
        if (string.IsNullOrEmpty(input.GenerateType))
            input.GenerateType = "200";

        List<string> templatePathList = new List<string>();
        List<string> targetPathList = new List<string>();
        string zipPath = string.Empty;
        if (type == CodeGenType.PC)
        {
            // 先删除该表已生成的菜单列表
            templatePathList = GetTemplatePathList(input);
            zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName);
            if (input.GenerateType.StartsWith('1'))
            {
                targetPathList = GetZipPathList(input);
                if (Directory.Exists(zipPath))
                    Directory.Delete(zipPath, true);
            }
            else
                targetPathList = GetTargetPathList(input);
        }
        else
        {
            templatePathList = GetTemplatePathList(input, CodeGenType.APP);
            targetPathList = GetTargetPathList(input, CodeGenType.APP);
        }

        var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = input.Id }); // 字段集合
        var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合
        var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段
        (string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名

        input.NormalButtons.ForEach(r =>
        {
            switch (r.ButtonName)
            {
                case "add":
                    r.Order = 1;
                    break;
                case "export":
                    r.Order = 10;
                    break;
                case "templateDown":
                    r.Order = 20;
                    break;
                case "import":
                    r.Order = 30;
                    break;
                case "detail":
                    r.Order = 40;
                    break;
                case "save":
                    r.Order = 80;
                    break;
                case "edit":
                    r.Order = 90;
                    break;
                case "delete":
                    r.Order = 99;
                    break;
                default:
                    break;
            }
        });
        var data = new CustomViewEngine(_db)
        {
            ConfigId = input.ConfigId,
            AuthorName = input.AuthorName,
            BusName = input.BusName,
            NameSpace = input.NameSpace,
            ClassName = input.TableName,
            ProjectLastName = input.NameSpace.Split('.').Last(),
            QueryWhetherList = queryWhetherList,
            TableField = tableFieldList,
            CheckWhere = input.CheckWhere.IsNullOrEmpty() ? "id" : input.CheckWhere,
            Comments = (input.Comments == null ? new List<CodeGenComment>() : input.Comments),
            TabOption = (input.Comments == null || input.Comments.Where(r => r.IsMultipleOperation == "Tabs").Count() <= 0 ? null :
            (new
            {
                column = input.Comments.Where(r => r.IsMultipleOperation == "Tabs")
            .Select(r => new { label = r.ButtonName, prop = r.CommentName }).ToList()
            }).ToJson()
             ),
            IsJoinTable = joinTableList.Count > 0,
            IsUpload = joinTableList.Where(u => u.EffectType == "Upload").Any(),
            NormalButtons = input.NormalButtons.OrderBy(btn => btn.Order).ToList(),
            OpenHis = input.Temp1,
            IsMutleLine = input.Temp2,
            Temp3 = input.Temp3
        };
        if (!input.Temp5.IsNullOrEmpty())
            data.OverrideClassName = input.Temp5;
        else
            data.OverrideClassName = data.LowerClassName;

        if (!data.ProjectLastName.IsNullOrEmpty() && data.ProjectLastName == "Core")
            data.ProjectLastName = "";
        for (var i = 0; i < templatePathList.Count; i++)
        {
            var tContent = File.ReadAllText(templatePathList[i]);
            //表示是APP
            //if (Path.GetFileName(templatePathList[i]).StartsWith("app_index"))
            //{
            //    data.TableField = data.TableField.Where(r => r.NetType != "long?").ToList();
            //}

            var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder =>
            {
                builder.AddAssemblyReferenceByName("System.Linq");
                builder.AddAssemblyReferenceByName("System.Collections");
                builder.AddUsing("System.Collections.Generic");
                builder.AddUsing("System.Linq");
            });
            var dirPath = new DirectoryInfo(targetPathList[i]).Parent.FullName;
            if (!Directory.Exists(dirPath))
                Directory.CreateDirectory(dirPath);

            if (templatePathList[i].EndsWith("manage.js.vm"))
            {
                //确认是否重写了通用的增删改查
                var netPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName);
                string[] subfolders = Directory.GetDirectories(netPath, "*", SearchOption.TopDirectoryOnly);
                var extendDir = subfolders.FirstOrDefault(name => name.Contains("ApplicationExtend"));
                if (Directory.Exists(extendDir))
                {
                    // 获取所有文件，包括子文件夹中的文件
                    string[] files = Directory.GetFiles(extendDir, "*.cs", SearchOption.AllDirectories);
                    string extendFileName = $"{input.TableName}ExtendService";
                    var extendFilePath = files.FirstOrDefault(file => file.ToLower().Contains(extendFileName.ToLower()));
                    var readString = FileHelper.ReadFile(extendFilePath, Encoding.UTF8);

                    #region  Replace tResult Url to Extend Url
                    if (readString.Contains("Add("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/add", $"{data.OverrideClassName}Extend/add");
                    }
                    if (readString.Contains("Page("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/page", $"{data.OverrideClassName}Extend/page");
                    }
                    if (readString.Contains("Delete("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/delete", $"{data.OverrideClassName}Extend/delete");
                    }
                    if (readString.Contains("Update("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/update", $"{data.OverrideClassName}Extend/update");
                    }
                    if (readString.Contains("Save("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/save", $"{data.OverrideClassName}Extend/save");
                    }
                    if (readString.Contains("Export("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/export", $"{data.OverrideClassName}Extend/export");
                    }
                    if (readString.Contains("Import("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/export", $"{data.OverrideClassName}Extend/import");
                    }
                    if (readString.Contains("TemplateDown("))
                    {
                        tResult = tResult.Replace($"{data.OverrideClassName}/export", $"{data.OverrideClassName}Extend/TemplateDown");
                    }
                    #endregion
                }


            }


            File.WriteAllText(targetPathList[i], tResult, Encoding.UTF8);
        }

        List<string> messageList = new List<string>();
        //父子组件之更新子组件信息
        data.Comments.ForEach(r =>
        {
            if (
            ((r.ButtonAuth == "addBody" && r.IsMultipleOperation == "N") || (r.ButtonAuth == "expand" && r.IsMultipleOperation == "E"))
            && !r.AssField.IsNullOrEmpty() && !r.CommentPathTemp.IsNullOrEmpty())
            {
                var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src");
                var filePath = $"{frontendPath}/{r.CommentPathTemp.Trim().Split("@/")[1]}";
                if (File.Exists(filePath))
                {
                    var targetFile = FileHelper.ReadFile(filePath);
                    targetFile = targetFile
                    .Replace("style=\"width: 100%\"", "style=\"width: 90%\"")
                    .Replace("style=\"width:100%\"", "style=\"width: 90%\"")
                    .Replace("style=\"width:100%\"", "style=\"width: 90%\"");

                    //替换子组件与父组件关联的字段
                    string startStr = "//Code Gen Start !don't delete";
                    string endStr = "//Code Gen End !don't delete";
                    var startIndex = targetFile.IndexOf(startStr);
                    var endIndex = targetFile.IndexOf(endStr);
                    if (startIndex > -1 && endIndex > -1)
                    {
                        startIndex += startStr.Length;
                        var needReplaceStr = targetFile.Substring(startIndex, endIndex - startIndex);
                        if (needReplaceStr.Contains("props.info.id"))
                            targetFile = targetFile.Replace(needReplaceStr, $"\r\n              queryParams.value.{r.AssField} = props.info.id;            \r\n                ");
                        else if (needReplaceStr.Contains("row.id"))
                            targetFile = targetFile.Replace(needReplaceStr, $"\r\n           ruleForm.value.{r.AssField} =row.id;                       \r\n                ");
                    }
                    // 替换子组件按钮颜色
                    startStr = "<!-- //Code Gen Start Button1 !don't delete -->";
                    endStr = "<!-- //Code Gen End Button1 !don't delete -->";
                    startIndex = targetFile.IndexOf(startStr);
                    endIndex = targetFile.IndexOf(endStr);
                    if (startIndex > -1 && endIndex > -1)
                    {
                        startIndex += startStr.Length;
                        var needReplaceStr = targetFile.Substring(startIndex, endIndex - startIndex);
                        if (needReplaceStr.Contains("el-button"))
                        {
                            var needReplaceTargetStr = needReplaceStr
                            .Replace("type=\"primary\"", "type=\"success\"")
                            .Replace("<el-form-item>", "<el-form-item class=\"mt8 ml15\">");
                            targetFile = targetFile.Replace(needReplaceStr, $"\r\n              {needReplaceTargetStr}            \r\n                ");
                        }
                    }
                    // 替换子组件按钮颜色
                    startStr = "<!-- //Code Gen Start Button2 !don't delete -->";
                    endStr = "<!-- //Code Gen End Button2 !don't delete -->";
                    startIndex = targetFile.IndexOf(startStr);
                    endIndex = targetFile.IndexOf(endStr);
                    if (startIndex > -1 && endIndex > -1)
                    {
                        startIndex += startStr.Length;
                        var needReplaceStr = targetFile.Substring(startIndex, endIndex - startIndex);
                        if (needReplaceStr.Contains("el-button"))
                        {
                            var needReplaceTargetStr = needReplaceStr.Replace("type=\"primary\"", "type=\"success\"").Replace("type=\"warning\"", "type=\"success\"");
                            targetFile = targetFile.Replace(needReplaceStr, $"\r\n              {needReplaceTargetStr}            \r\n                ");
                        }
                    }

                    //筛选条件，展开删除
                    startStr = "<!-- //Code Gen QueryStart !don't delete -->";
                    endStr = "<!-- //Code Gen QueryEnd !don't delete -->";
                    startIndex = targetFile.IndexOf(startStr);
                    endIndex = targetFile.IndexOf(endStr);
                    if (startIndex > -1 && endIndex > -1)
                    {
                        startIndex += startStr.Length;
                        var needReplaceStr = targetFile.Substring(startIndex, endIndex - startIndex);
                        if (needReplaceStr.Contains("<el-collapse"))
                        {
                            startIndex = needReplaceStr.IndexOf("<el-form");
                            endIndex = needReplaceStr.IndexOf("</el-collapse-item>");
                            if (startIndex > -1 && endIndex > -1)
                            {
                                var needReplaceTargetStr = needReplaceStr.Substring(startIndex, endIndex - startIndex);
                                targetFile = targetFile.Replace(needReplaceStr, $"\r\n              {needReplaceTargetStr}            \r\n                ");
                            }

                        }
                    }
                    //新增明细弹出框，删除与父亲组件关联的字段不用显示
                    if (filePath.Contains("Dialog.vue"))
                    {
                        endStr = $"prop=\"{r.AssField}\">";
                        endIndex = targetFile.IndexOf(endStr);
                        if (endIndex > 0)
                        {
                            var tempSubStr = targetFile.Substring(0, endIndex + endStr.Length); //截取到 prop="ukRuleId"> 结束
                            startStr = "<el-col";
                            startIndex = tempSubStr.LastIndexOf(startStr); //找打 prop="ukRuleId">的前段 <el-form-item ，从这个位置开始截取
                            if (startIndex > 0)
                            {
                                var needReplaceStr = tempSubStr.Substring(startIndex);
                                var needReplaceTargetStr = needReplaceStr.Replace(">", " v-if='false'>").Replace(" v-if='false'>", " v-if='false'>");
                                targetFile = targetFile.Replace(needReplaceStr, $"{needReplaceTargetStr}");
                            }
                        }
                    }

                    //展开行筛选条件，列表列删除与主表关联字段
                    if (filePath.Contains("index.vue"))
                    {
                        #region 查询条件
                        endStr = $"v-model=\"queryParams.{r.AssField}\"";
                        endIndex = targetFile.IndexOf(endStr);
                        if (endIndex > 0)
                        {
                            var tempSubStr = targetFile.Substring(0, endIndex + endStr.Length);
                            startStr = "<el-form-item";
                            startIndex = tempSubStr.LastIndexOf(startStr);
                            if (startIndex > 0)
                            {
                                var needReplaceStr = tempSubStr.Substring(startIndex);
                                if (!needReplaceStr.Contains("v-if='false'"))
                                {
                                    var needReplaceTargetStr = needReplaceStr.Replace(">", " v-if='false'>").Replace(" v-if='false'>", " v-if='false'>");
                                    targetFile = targetFile.Replace(needReplaceStr, $"{needReplaceTargetStr}");
                                }
                            }
                        }
                        #endregion

                        #region 列表列
                        endStr = $"prop=\"{r.AssField}\"";
                        endIndex = targetFile.IndexOf(endStr);
                        if (endIndex > 0)
                        {
                            var tempSubStr = targetFile.Substring(0, endIndex + endStr.Length);
                            startStr = "<el-table-column";
                            startIndex = tempSubStr.LastIndexOf(startStr);
                            if (startIndex > 0)
                            {
                                var needReplaceStr = tempSubStr.Substring(startIndex);
                                if (!needReplaceStr.Contains("v-if"))
                                {
                                    var needReplaceTargetStr = needReplaceStr.Replace("<el-table-column", "<el-table-column v-if='false' ");
                                    targetFile = targetFile.Replace(needReplaceStr, $"{needReplaceTargetStr}");
                                }
                            }
                        }
                        #endregion
                    }


                    FileHelper.WriteFile(filePath, targetFile, Encoding.UTF8, true);
                }
                else
                {
                    messageList.Add($"未找到文件：{filePath} ......");
                }
            }
        });



        await AddMenu(input.TableName, input.BusName, input.MenuPid, tableFieldList, input.Comments, input.NormalButtons, type);

        if (input.Temp1)
        {
            var dataService = App.GetService<SysDatabaseService>();
            dataService.OneTableChange($"{input.TableName}_History");
        }
        // 非ZIP压缩返回空
        if (!input.GenerateType.StartsWith('1'))
            return null;
        else
        {
            string downloadPath = zipPath + ".zip";
            // 判断是否存在同名称文件
            if (File.Exists(downloadPath))
                File.Delete(downloadPath);
            ZipFile.CreateFromDirectory(zipPath, downloadPath);
            return new { url = $"{CommonUtil.GetLocalhost()}/CodeGen/{input.TableName}.zip" };
        }
    }

    /// <summary>
    /// 获取连表的实体名和别名
    /// </summary>
    /// <param name="configs"></param>
    /// <returns></returns>
    private (string, string) GetJoinTableStr(List<CodeGenConfig> configs)
    {
        var uploads = configs.Where(u => u.EffectType == "Upload").ToList();
        var fks = configs.Where(u => u.EffectType == "fk").ToList();
        string str = ""; // <Order, OrderItem, Custom>
        string lowerStr = ""; //(o, i, c)
        foreach (var item in uploads)
        {
            lowerStr += "sysFile_FK_" + item.LowerPropertyName + ",";
            str += "SysFile,";
        }
        foreach (var item in fks)
        {
            lowerStr += item.LowerFkEntityName + "_FK_" + item.LowerFkColumnName + ",";
            str += item.FkEntityName + ",";
        }
        return (str.TrimEnd(','), lowerStr.TrimEnd(','));
    }

    /// <summary>
    /// 增加菜单
    /// </summary>
    /// <param name="className"></param>
    /// <param name="busName"></param>
    /// <param name="pid"></param>
    /// <param name="tableFieldList"></param>
    /// <returns></returns>
    private async Task AddMenu(string className, string busName, long pid, List<CodeGenConfig> tableFieldList, List<CodeGenComment> Comments, List<NormalButton> NormalButtons, CodeGenType type = CodeGenType.PC)
    {
        var pPath = string.Empty;
        // 若 pid=0 为顶级则创建菜单目录
        if (pid == 0)
        {
            // 目录
            var menuType0 = new SysMenu
            {
                Pid = 0,
                Title = busName + "",
                Type = MenuTypeEnum.Dir,
                Icon = "robot",
                Path = "/" + className.ToLower(),
                Component = "LAYOUT",
            };
            // 若先前存在则删除本级和下级
            var menuList0 = await _db.Queryable<SysMenu>().Where(e => e.Title == menuType0.Title && e.Type == menuType0.Type).ToListAsync();
            if (menuList0.Count > 0)
            {
                var listIds = menuList0.Select(f => f.Id).ToList();
                var childlistIds = new List<long>();
                foreach (var item in listIds)
                {
                    var childlist = await _db.Queryable<SysMenu>().ToChildListAsync(u => u.Pid, item);
                    childlistIds.AddRange(childlist.Select(f => f.Id).ToList());
                }
                listIds.AddRange(childlistIds);
                #region 原代码注释 2023-11-21
                //await _db.Deleteable<SysMenu>().Where(e => listIds.Contains(e.Id)).ExecuteCommandAsync();
                //await _db.Deleteable<SysRoleMenu>().Where(e => listIds.Contains(e.MenuId)).ExecuteCommandAsync(); 
                #endregion
                #region 新增代码-解决代码重新生成冲掉角色权限问题 2023-11-21
                await _db.Updateable<SysMenu>().SetColumns(r => new SysMenu { IsDelete = true, UpdateTime = DateTime.Now }).Where(e => listIds.Contains(e.Id)).ExecuteCommandAsync();
                await _db.Updateable<SysRoleMenu>().SetColumns(r => new SysRoleMenu { IsDelete = true }).Where(e => listIds.Contains(e.MenuId)).ExecuteCommandAsync();
                #endregion
            }
            #region 原代码注释 2023-11-21
            //pid = (await _db.Insertable(menuType0).ExecuteReturnEntityAsync()).Id;
            #endregion
            #region 新增代码-解决代码重新生成冲掉角色权限问题 2023-11-21
            pid = default(long);
            if (menuList0.Count > 0)
            {
                menuList0.ForEach(r =>
                {
                    r.IsDelete = false;
                });
                await _db.Updateable(menuList0).ExecuteCommandAsync();

                //修改已存在的菜单权限
                await _db.Updateable<SysRoleMenu>()
                    .SetColumns(r => new SysRoleMenu { IsDelete = false })
                    .Where(r => menuList0.Select(c => c.Id).Contains(r.MenuId)).ExecuteCommandAsync();

                pid = menuList0.FirstOrDefault().Id;
            }
            else
                pid = (await _db.Insertable(menuList0).ExecuteReturnEntityAsync()).Id;
            #endregion
        }
        else
        {
            var pMenu = await _db.Queryable<SysMenu>().FirstAsync(e => e.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505);
            pPath = pMenu.Path;
        }
        // 菜单
        var menuType1 = new SysMenu
        {
            Pid = pid,
            Title = busName + "",
            Name = className[..1].ToLower() + className[1..],
            Type = MenuTypeEnum.Menu,
            Path = pPath + "/" + className.ToLower(),
            Component = "/main/" + className[..1].ToLower() + className[1..] + "/index",
        };
        if (type == CodeGenType.APP)
        {
            menuType1.LinkUrl = "/pages/" + className[..1].ToLower() + className[1..] + "/index";
            menuType1.EditUrl = "/pages/" + className[..1].ToLower() + className[1..] + "/component/editDialog";
            menuType1.IsAppShow = true;
            menuType1.Badge = 0;
            WriteAppPagesJson(menuType1);
        }

        await AddSysPageOptionConfig(menuType1.Component, menuType1.Name, busName, tableFieldList);
        // 若先前存在则删除本级和下级
        var menuList1 = await _db.Queryable<SysMenu>().Where(e => e.Title == menuType1.Title && e.Type == menuType1.Type).ToListAsync();
        if (menuList1.Count > 0)
        {
            var tempModel = menuList1.FirstOrDefault(e => e.Title == menuType1.Title && e.Type == menuType1.Type);
            if (tempModel != null && type == CodeGenType.APP)
            {
                tempModel.LinkUrl = menuType1.LinkUrl;
                tempModel.IsAppShow = menuType1.IsAppShow;
                tempModel.IsHide = menuType1.IsHide;
                tempModel.Badge = menuType1.Badge;
                await _db.MyUpdateable<SysMenu>(tempModel).UpdateColumns(r => new
                {
                    r.LinkUrl,
                    r.IsAppShow,
                    r.Badge,
                    r.Component
                }).ExecuteCommandAsync();
            }
            var listIds = menuList1.Select(f => f.Id).ToList();
            var childlistIds = new List<long>();
            foreach (var item in listIds)
            {
                var childlist = await _db.Queryable<SysMenu>().ToChildListAsync(u => u.Pid, item);
                childlistIds.AddRange(childlist.Select(f => f.Id).ToList());
            }
            listIds.AddRange(childlistIds);

            #region 原代码注释 2023-11-21
            //await _db.Deleteable<SysMenu>().Where(e => listIds.Contains(e.Id)).ExecuteCommandAsync();
            //await _db.Deleteable<SysRoleMenu>().Where(e => listIds.Contains(e.MenuId)).ExecuteCommandAsync(); 
            #endregion
            #region 新增代码-解决代码重新生成冲掉角色权限问题 2023-11-21
            await _db.Updateable<SysMenu>().SetColumns(r => new SysMenu { IsDelete = true, UpdateTime = DateTime.Now }).Where(e => listIds.Contains(e.Id)).ExecuteCommandAsync();
            await _db.Updateable<SysRoleMenu>().SetColumns(r => new SysRoleMenu { IsDelete = true }).Where(e => listIds.Contains(e.MenuId)).ExecuteCommandAsync();

            #endregion
        }
        #region 原代码注释 2023-11-21
        //pid1 = (await _db.Insertable(menuType1).ExecuteReturnEntityAsync()).Id;
        #endregion
        #region 新增代码-解决代码重新生成冲掉角色权限问题 2023-11-21
        long pid1 = default(long);
        if (menuList1.Count > 0)
        {
            menuList1.ForEach(r =>
            {
                r.IsDelete = false;
            });
            await _db.Updateable(menuList1).IgnoreColumns(new string[] { nameof(SysMenu.Color), nameof(SysMenu.AppIcon) }).ExecuteCommandAsync();

            //修改已存在的菜单权限
            await _db.Updateable<SysRoleMenu>()
                .SetColumns(r => new SysRoleMenu { IsDelete = false })
                .Where(r => menuList1.Select(c => c.Id).Contains(r.MenuId)).ExecuteCommandAsync();

            pid1 = menuList1.FirstOrDefault().Id;
        }
        else
            pid1 = (await _db.Insertable(menuType1).ExecuteReturnEntityAsync()).Id;
        #endregion
        int menuOrder = 101;
        // 按钮-page
        var menuType2 = new SysMenu
        {
            Pid = pid1,
            Title = "查询",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":page",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        // 按钮-detail
        var menuType2_1 = new SysMenu
        {
            Pid = pid1,
            Title = "详情",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":detail",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        // 按钮-add
        var menuType2_2 = new SysMenu
        {
            Pid = pid1,
            Title = "新增",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":add",
            LinkUrl = $"/pages/{className[..1].ToLower() + className[1..]}/component/editDialog?params=",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        // 按钮-delete
        var menuType2_3 = new SysMenu
        {
            Pid = pid1,
            Title = "删除",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":delete",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        // 按钮-edit
        var menuType2_4 = new SysMenu
        {
            Pid = pid1,
            Title = "编辑",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":edit",
            LinkUrl = $"/pages/{className[..1].ToLower() + className[1..]}/component/editDialog?params=",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        // 按钮-edit
        var menuType2_5 = new SysMenu
        {
            Pid = pid1,
            Title = "导出",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":export",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        var menuType2_6 = new SysMenu
        {
            Pid = pid1,
            Title = "导入",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":import",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        var menuType2_7 = new SysMenu
        {
            Pid = pid1,
            Title = "下载导入模板",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":templateDown",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        // 按钮-save
        var menuType2_8 = new SysMenu
        {
            Pid = pid1,
            Title = "保存",
            Type = MenuTypeEnum.Btn,
            Permission = className[..1].ToLower() + className[1..] + ":save",
            LinkUrl = $"/pages/{className[..1].ToLower() + className[1..]}/component/editDialog?params=",
            OrderNo = menuOrder
        };
        menuOrder += 3;

        var menuList = new List<SysMenu>() { menuType2_1, menuType2_2, menuType2_3, menuType2_4, menuType2_5, menuType2_6, menuType2_7, menuType2_8 };

        var needAuths = NormalButtons.Where(r => r.IsUse == true).Select(r => r.Desc).ToList();
        menuList = menuList.Where(r => needAuths.Contains(r.Title)).ToList();
        menuList.Add(menuType2);
        if (Comments != null && Comments.Count > 0)
        {
            foreach (var comment in Comments)
            {
                if (comment.ButtonAuth.IsNullOrEmpty())
                    continue;
                var customeBtn = new SysMenu
                {
                    Pid = pid1,
                    Title = comment.ButtonName,
                    Type = MenuTypeEnum.Btn,
                    Permission = className[..1].ToLower() + className[1..] + $":{comment.ButtonAuth}",
                    LinkUrl = $"/pages/{className[..1].ToLower() + className[1..]}/component/{comment.ButtonAuth}?params=",
                    OrderNo = menuOrder
                };
                menuList.Add(customeBtn);
            }
        }


        // 加入fk、Upload、ApiTreeSelect 等接口的权限
        // 在生成表格时，有些字段只是查询时显示，不需要填写（WhetherAddUpdate），所以这些字段没必要生成相应接口
        var fkTableList = tableFieldList.Where(u => u.EffectType == "fk" && (u.WhetherAddUpdate == "Y" || u.QueryWhether == "Y")).ToList();
        foreach (var @column in fkTableList)
        {
            var menuType = new SysMenu
            {
                Pid = pid1,
                Title = "外键" + @column.ColumnName,
                Type = MenuTypeEnum.Btn,
                Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + column.ColumnName + "Dropdown",
                OrderNo = menuOrder
            };
            menuOrder += 3;
            menuList.Add(menuType);
        }
        var treeSelectTableList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelect").ToList();
        foreach (var @column in treeSelectTableList)
        {
            var menuType = new SysMenu
            {
                Pid = pid1,
                Title = "树型" + @column.ColumnName,
                Type = MenuTypeEnum.Btn,
                Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + "Tree",
                OrderNo = menuOrder
            };
            menuOrder += 3;
            menuList.Add(menuType);
        }
        var uploadTableList = tableFieldList.Where(u => u.EffectType == "Upload").ToList();
        foreach (var @column in uploadTableList)
        {
            var menuType = new SysMenu
            {
                Pid = pid1,
                Title = "上传" + @column.ColumnName,
                Type = MenuTypeEnum.Btn,
                Permission = className[..1].ToLower() + className[1..] + ":Upload" + column.ColumnName,
                OrderNo = menuOrder
            };
            menuOrder += 3;
            menuList.Add(menuType);
        }
        //数据库存在这个权限就更新这个权限
        var dbMenus = await _db.Queryable<SysMenu>().Where(r => r.Pid == pid1 && menuList.Select(c => c.Permission).Contains(r.Permission)).ToListAsync();
        if (dbMenus.Count > 0)
        {
            dbMenus.ForEach(r =>
            {
                var tempMenu = menuList.FirstOrDefault(c => c.Permission == r.Permission);
                r.IsDelete = false;
                r.Title = tempMenu.Title;
                r.Type = tempMenu.Type;
                r.LinkUrl = tempMenu.LinkUrl;
                r.OrderNo = tempMenu.OrderNo;
            });
            //已存在的按钮
            await _db.Updateable<SysMenu>(dbMenus).ExecuteCommandAsync();
            //修改已存在的按钮权限
            await _db.Updateable<SysRoleMenu>()
                .SetColumns(r => new SysRoleMenu { IsDelete = false })
                .Where(r => dbMenus.Select(c => c.Id).Contains(r.MenuId)).ExecuteCommandAsync();
            //取差集
            menuList = menuList.Where(r => !dbMenus.Select(c => c.Permission).Contains(r.Permission)).ToList();
        }
        if (menuList.Count > 0)
            await _db.Insertable(menuList).ExecuteCommandAsync();

        _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserButton);
        _sysCacheService.RemoveByPrefixKey(CacheConst.KeyUserAppButton);
    }

    private async Task AddSysPageOptionConfig(string configPath, string configCode, string configName, List<CodeGenConfig> tableFieldList)
    {
        var option = await _db.Queryable<SysPageOptionConfig>()
            .Includes(r => r.column)
            .FirstAsync(r => r.configPath == configPath);

        List<SysPageOptionConfigColumn> addColumnList = new List<SysPageOptionConfigColumn>();
        List<SysPageOptionConfigColumn> updateColumnList = new List<SysPageOptionConfigColumn>();
        if (option == null)
            option = new SysPageOptionConfig
            {
                configPath = configPath,
                configName = configName,
                configCode = configCode
            };
        if (option.column == null)
            option.column = new List<SysPageOptionConfigColumn>();

        var normalTypeInit = (SysPageOptionConfigColumn col) =>
        {
            col.dicUrl = null; //需赋值
            col.props = null;
            col.dicFormatter = null;
            col.dicMethod = null;
            col.dicQuery = null;
            col.dicData = null;
            col.checkStrictly = null;
            col.searchCheckStrictly = null;
            col.show_all_levels = null;
        };
        foreach (var r in tableFieldList)
        {
            var lowerColumn = r.ColumnName[..1].ToLower() + r.ColumnName[1..];
            var column = option.column.FirstOrDefault(c => c.prop == lowerColumn);
            if (column == null)
            {
                column = new SysPageOptionConfigColumn();
                column.prop = lowerColumn;
                column.configPath = option.configPath;
            }
            column.label = r.ColumnComment;
            switch (r.EffectType)
            {
                case "DatePicker":
                    normalTypeInit(column);
                    //column.type = ColumnType.date.GetDescription();
                    column.type = ColumnType.datetime.GetDescription();
                    column.searchRange = true;
                    //column.format = "YYYY-MM-DD HH:mm:ss";
                    //column.valueFormat = "YYYY-MM-DD HH:mm:ss";
                    break;
                case "Switch":
                    normalTypeInit(column);
                    column.type = ColumnType.switch_.GetDescription();
                    //column.props = new ColumnPropMapperInfo { label = nameof(ColumnProps.label), value = nameof(ColumnProps.value) };
                    //column.dicData = new List<ColumnProps> {
                    //new ColumnProps {  label ="否",value = 0},
                    //new ColumnProps {  label ="是",value = 1},
                    //};
                    column.props = null;
                    column.dicData = null;
                    break;
                case "InputTextArea":
                    normalTypeInit(column);
                    column.type = ColumnType.textarea.GetDescription();
                    column.span = 24;
                    //column.maxlength = r.ColumnLength;
                    break;
                case "InputNumber":
                    normalTypeInit(column);
                    column.type = ColumnType.number.GetDescription();
                    break;
                case "Input":
                    normalTypeInit(column);
                    column.type = ColumnType.input.GetDescription();
                    column.maxlength = r.ColumnLength;
                    break;
                case "KeyValueList":
                    normalTypeInit(column);
                    column.type = ColumnType.date.GetDescription();
                    break;
                case "fk":
                    normalTypeInit(column);
                    column.type = ColumnType.select.GetDescription();
                    column.dicUrl = $"/api/{configCode}/{r.FkEntityName[..1].ToLower() + r.FkEntityName[1..]}{r.PropertyName}Dropdown";
                    column.props = new ColumnPropMapperInfo();
                    column.dicMethod = "post";
                    column.dicQuery = null;
                    column.dicFormatter = "(res)=>{ return res.result;}";
                    break;
                case "EnumSelector":
                case "ConstSelector":
                    column.type = ColumnType.select.GetDescription();
                    if (column.Id <= 0)
                    {
                        column.filterable = true;
                        column.virtualize = true;
                    }
                    var enums = EnumExtension.GetEnumByName(r.DictTypeCode).GetEnumDescDictionary();
                    column.props = new ColumnPropMapperInfo { label = nameof(ColumnProps.label), value = nameof(ColumnProps.value) };
                    column.dicData = enums.Select(em => new ColumnProps
                    {
                        label = em.Value,
                        value = em.Key
                    }).ToList();
                    break;
                case "Cascader":
                    column.type = ColumnType.cascader.GetDescription();
                    if (column.Id <= 0)
                    {
                        column.checkStrictly = true;
                        column.searchCheckStrictly = true;
                        column.filterable = true;
                        //前端识别 dicUrl有值，则会再dicUrl前添加后台API地址，如：http://localhost:5000/api/Common/optionConfig
                        column.dicUrl = null;//  应该这样 /api/Common/optionConfig ; //需赋值(只需要赋值后端的接口地址 ，IP由前端自动拼接)
                        column.props = new ColumnPropMapperInfo { label = "label", value = "value" };
                        column.dicFormatter = "(res)=>{return res.result;}";
                    }
                    break;
                case "UploadMore":
                    normalTypeInit(column);
                    column.type = ColumnType.upload.GetDescription();
                    string functionName = "UploadSolveFile";
                    column.action = $"/api/{configCode}/{functionName[..1].ToLower() + functionName[1..]}";
                    column.propsHttp = new PropsHttpModel();

                    break;
                default:
                    normalTypeInit(column);
                    column.type = ColumnType.input.GetDescription();
                    column.maxlength = r.ColumnLength;
                    break;
            }
            column.searchType = column.type.GetDescription();
            if (column.Id <= 0)
            {
                column.order = r.OrderNo * -1;
                column.hide = (r.WhetherTable == "Y" ? false : true); //列隐藏
                column.showColumn = (r.WhetherTable == "Y" ? true : false); //参与列显示隐藏
                column.display = (r.WhetherAddUpdate == "Y" ? true : false); //添加修改编辑隐藏
                column.search = (r.QueryWhether == "Y" ? true : false); //是否搜索
                column.cell = (r.Temp1 == "Y" ? true : false); //是否列表直接编辑
                column.rules = (r.WhetherRequired == "Y" ? new List<Rule> { new Rule { required = true, message = $"{column.label}为必填项！" } } : null);
                column.width = column.label == null ? column.width : column.label.Length * 50;
            }
            if (column.maxlength.HasValue && column.maxlength.Value == -1)
                column.maxlength = 255;

            if (column.Id <= 0)
                addColumnList.Add(column);
            else
                updateColumnList.Add(column);
        };
        if (option.Id > 0)
            await _db.MyUpdateable<SysPageOptionConfig>(option).UpdateColumns(r => new { r.configPath, r.configName, r.configCode }).ExecuteCommandAsync();
        else
            await _db.MyInsertable<SysPageOptionConfig>(option).ExecuteCommandAsync();

        if (addColumnList.Count > 0)
            await _db.MyInsertable<SysPageOptionConfigColumn>(addColumnList).ExecuteCommandAsync();

        if (updateColumnList.Count > 0)
            await _db.MyUpdateable<SysPageOptionConfigColumn>(updateColumnList).ExecuteCommandAsync();

        _sysCacheService.Set($"{CacheConst.KeyPageConfig}_{option.configPath}", option);
    }
    /// <summary>
    /// 获取模板文件路径集合
    /// </summary>
    /// <returns></returns>
    private static List<string> GetTemplatePathList(SysCodeGen input, CodeGenType type = CodeGenType.PC)
    {
        var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template");
        if (type == CodeGenType.PC)
        {
            #region PC
            if (input.GenerateType.Substring(1, 1).Contains('1'))
            {
                return new List<string>()
            {
                Path.Combine(templatePath , "index.vue.vm"),
                Path.Combine(templatePath , "editDialog.vue.vm"),
                Path.Combine(templatePath , "manage.js.vm"),
            };
            }
            else if (input.GenerateType.Substring(1, 1).Contains('2'))
            {
                return new List<string>()
            {
                Path.Combine(templatePath , "Service.cs.vm"),
                Path.Combine(templatePath , "Input.cs.vm"),
                Path.Combine(templatePath , "Output.cs.vm"),
                Path.Combine(templatePath , "Dto.cs.vm"),
            };
            }
            else
            {
                var list = new List<string>()
            {
                Path.Combine(templatePath , "Service.cs.vm"),
                Path.Combine(templatePath , "Input.cs.vm"),
                Path.Combine(templatePath , "Output.cs.vm"),
                Path.Combine(templatePath , "Dto.cs.vm"),
                Path.Combine(templatePath ,(input.GenerateType =="201"?"index_standard.vue.vm": "index.vue.vm")), //avue插件
                Path.Combine(templatePath , "editDialog.vue.vm"),
                Path.Combine(templatePath , "manage.js.vm"),
            };

                if (input.Temp1)
                {
                    list.Add(Path.Combine(templatePath, "History.cs.vm"));
                }
                return list.Where(r => !r.IsNullOrEmpty()).ToList();
            }
            #endregion
        }
        else
        {
            var list = new List<string>()
            {
                Path.Combine(templatePath ,"app_index.vue.vm"),
                Path.Combine(templatePath , "app_editDialog.vue.vm"),
                Path.Combine(templatePath , "app_Manage.js.vm"),
            };
            return list.Where(r => !r.IsNullOrEmpty()).ToList();
        }
    }

    /// <summary>
    /// 获取模板文件路径集合
    /// </summary>
    /// <returns></returns>
    private static List<string> GetTemplatePathList()
    {
        var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template");
        return new List<string>()
        {
            Path.Combine(templatePath , "Service.cs.vm"),
            Path.Combine(templatePath , "Input.cs.vm"),
            Path.Combine(templatePath , "Output.cs.vm"),
            Path.Combine(templatePath , "Dto.cs.vm"),
            Path.Combine(templatePath , "index.vue.vm"),
            Path.Combine(templatePath , "editDialog.vue.vm"),
            Path.Combine(templatePath , "manage.js.vm"),
        };
    }

    /// <summary>
    /// 设置生成文件路径
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [NonAction]
    private List<string> GetTargetPathList(SysCodeGen input, CodeGenType type = CodeGenType.PC)
    {
        if (type == CodeGenType.PC)
        {
            string backendPath = string.Empty;
            //var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName);
            if (input.NameSpace == "EMC.VISITOR.Plugin.GoView")
                backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, "Plugins", input.NameSpace, "Service", input.TableName);
            else
                backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.NameSpace, "Service", input.TableName);
            var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs");

            var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs");
            var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs");
            var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs");
            var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.FrontRootPath, "src", "views", "main");
            var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue");//
            var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue");
            var apiJsPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.FrontRootPath, "src", "api", "main", input.TableName[..1].ToLower() + input.TableName[1..] + ".ts");

            var list = new List<string>()
        {
            servicePath,
            inputPath,
            outputPath,
            viewPath,
            indexPath,
            formModalPath,
            apiJsPath
        };
            if (input.Temp1)
            {
                var entityF = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.NameSpace, "Entity");
                var entityFHistoryPath = Path.Combine(entityF, input.TableName + "_History.cs");
                list.Add(entityFHistoryPath);
            }
            return list.Where(r => !r.IsNullOrEmpty()).ToList();
        }
        else
        {
            var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.AppCodeGenFile);
            var indexPath = Path.Combine(frontendPath, "pages", input.TableName[..1].ToLower() + input.TableName[1..], "index.vue");//
            var formModalPath = Path.Combine(frontendPath, "pages", input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue");
            var apiJsPath = Path.Combine(frontendPath, "api", input.TableName[..1].ToLower() + input.TableName[1..] + ".js");
            var list = new List<string>()
        {
            indexPath,
            formModalPath,
            apiJsPath
        };
            return list;
        }
    }

    /// <summary>
    /// 设置生成文件路径
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [NonAction]
    public List<string> GetTargetPathListByAvue(SysCodeGen input)
    {
        //var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName);
        var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.NameSpace, "Service", input.TableName);
        var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs");

        var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs");
        var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs");
        var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs");
        var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.FrontRootPath, "src", "views", "main");
        var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index_standard.vue");//
        var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue");
        var apiJsPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.FrontRootPath, "src", "api", "main", input.TableName[..1].ToLower() + input.TableName[1..] + ".ts");

        var list = new List<string>()
        {
            servicePath,
            inputPath,
            outputPath,
            viewPath,
            indexPath,
            formModalPath,
            apiJsPath
        };
        if (input.Temp1)
        {
            var entityF = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.NameSpace, "Entity");
            var entityFHistoryPath = Path.Combine(entityF, input.TableName + "_History.cs");
            list.Add(entityFHistoryPath);
        }
        return list;
    }

    /// <summary>
    /// 设置生成文件路径
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    private List<string> GetZipPathList(SysCodeGen input)
    {
        var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName);

        //var backendPath = Path.Combine(zipPath, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName);
        var backendPath = Path.Combine(zipPath, input.NameSpace, "Service", input.TableName);
        var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs");
        var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs");
        var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs");
        var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs");
        var frontendPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "views", "main");
        var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue");
        var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue");
        var apiJsPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "api", "main", input.TableName[..1].ToLower() + input.TableName[1..] + ".ts");
        if (input.GenerateType.StartsWith("11"))
        {
            return new List<string>()
            {
                indexPath,
                formModalPath,
                apiJsPath
            };
        }
        else if (input.GenerateType.StartsWith("12"))
        {
            return new List<string>()
            {
                servicePath,
                inputPath,
                outputPath,
                viewPath
            };
        }
        else
        {
            return new List<string>()
            {
                servicePath,
                inputPath,
                outputPath,
                viewPath,
                indexPath,
                formModalPath,
                apiJsPath
            };
        }
    }
}